import 'dart:async';

import 'package:family_expense/features/authentication/widgets/password_login.dart';
import 'package:family_expense/main_view.dart';
import 'package:family_expense/shared/config/route_map_config.dart';
import 'package:family_expense/shared/config/theme_data_config.dart';
import 'package:family_expense/shared/constants/data_sharing_key.dart'
    as constants;
import 'package:family_expense/shared/models/system_setting.dart';
import 'package:family_expense/shared/services/data_initialization.dart';
import 'package:family_expense/shared/services/system_setting_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:my_flutter_core/my_flutter_core.dart';

Future<void> main() async {
  RouteMapConfigService.setNavigatorMapConfig(RouteMapConfig.navigatorConfig);

  UrlConfig.setUrls(
    apiHostBaseUrl: kReleaseMode
        ? 'https://zuiwen.cn'
        : 'http://192.168.2.56:8088',
  );

  // This captures errors reported by the Flutter framework.
  FlutterError.onError = (FlutterErrorDetails details) async {
    if (!kReleaseMode) {
      // In development mode simply print to console.
      FlutterError.dumpErrorToConsole(details);
    } else {
      // In production mode report to the application zone to report to remote server.
      Zone.current.handleUncaughtError(details.exception, details.stack!);
    }
  };

  // This creates a [Zone] that contains the Flutter application and stablishes
  // an error handler that captures errors and reports them.
  //
  // Using a zone makes sure that as many errors as possible are captured,
  // including those thrown from [Timer]s, microtasks, I/O, and those forwarded
  // from the `FlutterError` handler.
  //
  // More about zones:
  //
  // - https://api.dartlang.org/stable/1.24.2/dart-async/Zone-class.html
  // - https://www.dartlang.org/articles/libraries/zones
  runZonedGuarded<Future<void>>(
    () async {
      // Sets portraitUp orientation only.
      WidgetsFlutterBinding.ensureInitialized();
      await SystemChrome.setPreferredOrientations([
        DeviceOrientation.portraitUp,
      ]);

      runApp(const MyApp());
    },
    (error, stackTrace) async {
      // If device not connected to any network.
      if (await hasNetworkConnection() == true) {
        // showToast('发生未知错误：$error');

        // Report errors to a service.
        // https://flutter.dev/docs/cookbook/maintenance/error-reporting
        //
        // Whenever an error occurs, call the `_reportError` function. This sends
        // Dart errors to the dev console or Sentry depending on the environment.
        await AppErrorReport().send(error, stackTrace);
      }
    },
  );
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  // This widget is the root of the application.
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Indicates if the initialization is done.
  bool _isInitCompleted = false;

  // IMPORTANT!!!
  // The [build] is called twice even before the initialization is done which would cause duplicate back server access.
  // Uses this to avoid such unnecessary access.
  bool _isInitializing = false;

  // During app startup if detected user has been logged out we need to show [PasswordLogin].
  bool _loggedOut = false;

  bool? _isAuthenticated;
  SystemSetting? _systemSetting;
  SystemSetting? _changedSystemSetting;

  @override
  void initState() {
    super.initState();

    _setupLogoutListener();
    _setupSystemSettingListener();
    _loadUserAuthenticationStatus();
    _loadSystemSetting();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Family Expense',
      theme: ThemeDataConfig.getTheme(
        context: context,
        savedColor: _systemSetting?.themeSetting?.primaryColor,
        changedColor: _changedSystemSetting?.themeSetting?.primaryColor,
      ),
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [Locale('zh', 'CH'), Locale('en', 'US')],
      // The first page depends on a Future value.
      // That's why we are using [FutureProvider] and [Consumer] instead of specifying [initialRoute] property.
      //
      // IMPORTANT!!!
      // When using home property the route table CANNOT contain a route with value of [Navigator.defaultRouteName].
      // If so, when you use [routes] property then error occurs.
      // Error: If the home property is specified, the routes table cannot include an entry for "/", since it would be redundant.
      //
      // But when you work with [onGenerateRoute] property you have the bad luck. Because no any error tells what is wrong.
      // The only behavior is nothing happens when you call [Navigator.pushNamed].
      // Hope this helps.
      home: _buildAuthenticationCheckFuture(),
    );
  }

  /// Builds the [FutureProvider] for current user authentication check.
  /// It method will be called twice. One is by authentication check future and other is by system setting check future.
  ///
  /// It shows [MainView], [LogoView] or login view based on authentication status.
  Widget _buildAuthenticationCheckFuture() {
    // return const WebSocketDemo();

    if (_loggedOut) {
      return const PasswordLogin();
    }

    if (_isAuthenticated == false) {
      // If user is not authenticated.
      return const PasswordLogin();
    } else if (_isAuthenticated == true) {
      // If user is authenticated but system data is NOT ready yet.
      if (!_isInitCompleted) {
        // The data initialization must be called if it is not running and done.
        if (!_isInitCompleted && !_isInitializing) {
          _isInitializing = true;

          DataInitialization().init(
            () {
              if (mounted) {
                setState(() {
                  _isInitializing = false;
                  _isInitCompleted = true;
                });
              }
            },
            onError: (err) {
              if (err is UnauthorizedError || err is ForbiddenError) {
                // Shows user login view. This works during app startup.
                // Any other http request caused such error will be handled by logout notifier.
                return const PasswordLogin();
              }
            },
          );
        }

        // Shows [LogoView] when currently loading system data.
        return const LogoView();
      } else {
        // If user is authenticated and system data is ready.
        return const MainView();
      }
    } else {
      // If user authentication is not determined yet.
      return const LogoView();
    }
  }

  Future<void> _loadUserAuthenticationStatus() async {
    final isAuthenticated = await AuthStateService.instance.isAuthenticated;

    if (mounted) {
      setState(() => _isAuthenticated = isAuthenticated);
    }
  }

  Future<void> _loadSystemSetting() async {
    // Loads system setting from local storage.
    final setting = await SystemSettingService.setting;

    if (mounted) {
      setState(() => _systemSetting = setting);
    }
  }

  void _setupSystemSettingListener() {
    // Sets system setting listener to handle system setting change.
    final systemSettingChangeNotifier = SimpleChangeNotifier<SystemSetting>();

    systemSettingChangeNotifier.addListener(() {
      if (mounted) {
        setState(
          () => _changedSystemSetting = systemSettingChangeNotifier.value,
        );
      }
    });

    DataSharing.set(
      constants.DataSharingKey.systemSettingChangeNotifier,
      systemSettingChangeNotifier,
    );
  }

  /// Sets logout listener do handle unauthorized error from http request service during app startup.
  void _setupLogoutListener() {
    final logoutChangeNotifier = SimpleChangeNotifier<LogoutNotifier>(
      initialValue: LogoutNotifier(_logoutListener),
    );

    logoutChangeNotifier.addListener(_logoutListener);

    DataSharing.set(LogoutNotifier.key, logoutChangeNotifier);
  }

  void _logoutListener() {
    if (mounted) {
      setState(() => _loggedOut = true);
    }
  }
}
